/* Copyright 2007 Ryan Phillips.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <curl/curl.h>
#include "xblf.h"
#include "xblf_private.h"

const char *XBLF_USERAGENT = "libxblf v0.1";

void xblf_init()
{
    curl_global_init(CURL_GLOBAL_ALL);
    xmlInitParser();
}

void xblf_finish()
{
    xmlCleanupParser();
    curl_global_cleanup();
}

xblf_friend_t* xblf_friend_new()
{
    xblf_friend_t *f = calloc(1, sizeof(xblf_friend_t));
    f->failed = XBLF_ERROR_NOTQUERIED;
    return f;
}

xblf_friend_t* xblf_friend_new_by_tag(const char *gamertag)
{
    xblf_friend_t *f = xblf_friend_new();
    if (f) {
        xblf_friend_set_gamertag(f, gamertag);
    }
    return f;
}

void xblf_friend_free(xblf_friend_t *f)
{
    if (f->gamertag) free(f->gamertag);
    if (f->uri)      free(f->uri);
    if (f->xc) {
        xmlFreeParserCtxt(f->xc);
    }
    free(f);
}

void xblf_friend_set_gamertag(xblf_friend_t *f, const char *gamertag)
{
    f->gamertag = strdup(gamertag);
}

xblf_error_t xblf_friend_query(xblf_friend_t *f)
{
    CURL *curl;
    const char *BASE_URL_FMT = "http://duncanmackenzie.net/services/GetXboxInfo.aspx?GamerTag=%s";
    char url[1024], *escaped_gamertag;
    xblf_error_t rv = XBLF_ERROR_SUCCESS;

    if (!f->gamertag) {
        return XBLF_ERROR_MEMORY;
    }

    if (f->uri) {
        free(f->uri);
        f->uri = NULL;
    }

    if (f->xc) {
        xmlFreeParserCtxt(f->xc);
        f->xc = NULL;
    }

    /* HTML escape the gamertag and generate URL */
    escaped_gamertag = curl_escape(f->gamertag, strlen(f->gamertag));
    snprintf(url, sizeof(url), BASE_URL_FMT, escaped_gamertag);
    f->uri = strdup(url);
    curl_free(escaped_gamertag);

    /* Read xml from file for debugging */
    if (f->debug_filename) {
        f->xc = xmlNewParserCtxt();
        f->xc->myDoc = xmlCtxtReadFile(f->xc, f->debug_filename, NULL, 0);
        if (f->xc->myDoc)
            f->failed = XBLF_ERROR_SUCCESS;
        else 
            f->failed = XBLF_ERROR_PARSE;
    }
    else {
        /* Query using CURL */
        curl = curl_easy_init();

        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
        //curl_easy_setopt(curl, CURLOPT_VERBOSE, (void*) 1);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, xblf_write_callback);
        curl_easy_setopt(curl, CURLOPT_USERAGENT, XBLF_USERAGENT);
        curl_easy_setopt(curl, CURLOPT_URL, f->uri);

        /* Perform the fetch */
        if (curl_easy_perform(curl)) {
            f->failed = XBLF_ERROR_NET;
        }
        else {
            f->failed = XBLF_ERROR_SUCCESS;
        }

        curl_easy_cleanup(curl);
    }

    return f->failed;
}

static int xblf_is_valid_friend(xblf_friend_t *f)
{
    return f->failed == XBLF_ERROR_SUCCESS;
}

xblf_account_status
xblf_friend_get_account_status(xblf_friend_t *f)
{
    xblf_account_status status = XBLF_ACCOUNT_UNKNOWN;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return status;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        char *svalue = xblf_xpath_eval(f, xpath_ctx, 
                                      (const xmlChar*)"/XboxInfo/AccountStatus");

        if (svalue && !strcmp(svalue, "Gold")) {
            status = XBLF_ACCOUNT_GOLD;
        }
        else if (svalue && !strcmp(svalue,  "Silver")) {
            status = XBLF_ACCOUNT_SILVER;
        }

        free(svalue);
    }
    xmlXPathFreeContext(xpath_ctx);

    return status;
}

int xblf_friend_get_gamerscore(xblf_friend_t *f)
{
    int value = -1;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        char *svalue = xblf_xpath_eval(f, xpath_ctx, 
                                      (const xmlChar*)"/XboxInfo/GamerScore");
        if (svalue) {
            value = atoi(svalue);
        }

        free(svalue);
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char* xblf_friend_get_profileurl(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/ProfileUrl");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_get_tileurl(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/TileUrl");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_get_reputationurl(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/ReputationImageUrl");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

int
xblf_friend_presence_get_online(xblf_friend_t *f)
{
    int value = -1;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        char *svalue = xblf_xpath_eval(f, xpath_ctx, 
                                      (const xmlChar*)"/XboxInfo/PresenceInfo/Online");
        if (svalue) {
            value = (strcmp(svalue, "true") == 0);
        }
        free(svalue);
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_presence_get_info1(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/PresenceInfo/Info1");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_presence_get_info2(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/PresenceInfo/Info1");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

int
xblf_friend_presence_get_valid(xblf_friend_t *f)
{
    int value = -1;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        char *svalue = xblf_xpath_eval(f, xpath_ctx, 
                                      (const xmlChar*)"/XboxInfo/PresenceInfo/Valid");
        if (svalue) {
            value = !strcmp(svalue, "true");
        }
        free(svalue);
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_presence_get_status_text(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/PresenceInfo/StatusText");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_presence_get_title(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/PresenceInfo/Title");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

char*
xblf_friend_presence_lastseen(xblf_friend_t *f)
{
    char *value = NULL;
    xmlXPathContextPtr xpath_ctx;

    if (!xblf_is_valid_friend(f)) {
        return value;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        value = xblf_xpath_eval(f, xpath_ctx, 
                               (const xmlChar*)"/XboxInfo/PresenceInfo/LastSeen");
    }
    xmlXPathFreeContext(xpath_ctx);

    return value;
}

xblf_recentgames_t**
xblf_friend_get_recentgames(xblf_friend_t *f)
{
    xblf_recentgames_t **recent_games = NULL;
    xmlXPathContextPtr xpath_ctx;
    int count;

    if (!xblf_is_valid_friend(f)) {
        return NULL;
    }

    xpath_ctx = xmlXPathNewContext(f->xc->myDoc);
    if (xpath_ctx) {
        recent_games =  xblf_build_recentgames(f, 
                                               xpath_ctx, 
                                               (const xmlChar*)"/XboxInfo/RecentGames/XboxUserGameInfo", 
                                               &count);
    }
    xmlXPathFreeContext(xpath_ctx);

    return recent_games;
}

void
xblf_friend_recentgames_free(xblf_recentgames_t **recent_games)
{
    int i;

    if (!recent_games) {
        return;
    }

    for (i=0; recent_games[i]; i++) {
        xblf_recentgames_t *game = recent_games[i];

        if (game->last_played)      free(game->last_played);
        if (game->details_url)      free(game->details_url);
        if (game->game.image_32url) free(game->game.image_32url);
        if (game->game.image_64url) free(game->game.image_64url);
        if (game->game.name)        free(game->game.name);

        free(game);
    }

    free(recent_games);
}

